package com.a918.consensus;//import java.util.ArrayList;
//import java.util.Collections;
//import java.util.DoubleSummaryStatistics;
//import java.util.List;
//import java.util.Optional;
//import java.util.Random;
//import java.util.stream.Collectors;
//
//public class Main {
//  private static final int RANDOM_SEED = 12345;
//  private static final double TIME_LIMIT = 4;
//  private static final int SAMPLES = 1000;
//
//  public static void main(String[] args) {
//    // Print the first row which contains column names.
//    System.out.println("initial_timeout, tendermint, algorand, this_work");
//
//    double tendermintBestLatency = Double.MAX_VALUE, tendermintBestTimeout = 0;
//    double algorandBestLatency = Double.MAX_VALUE, algorandBestTimeout = 0;
//    double mirBestLatency = Double.MAX_VALUE, mirBestTimeout = 0;
//
//    for (double initalTimeout = 0.12; initalTimeout <= 0.4; initalTimeout += 0.01) {
//      DoubleSummaryStatistics tendermintOverallStats = new DoubleSummaryStatistics(),
//          algorandOverallStats = new DoubleSummaryStatistics(),
//          mirOverallStats = new DoubleSummaryStatistics();
//      for (int i = 0; i < SAMPLES; ++i) {
//        Optional<DoubleSummaryStatistics> tendermintStats =
//            runTendermint(initalTimeout, 90, 10);
//        Optional<DoubleSummaryStatistics> algorandStats =
//            runAlgorand(initalTimeout, 90, 10);
//        Optional<DoubleSummaryStatistics> mirStats =
//            runMir(initalTimeout, 90, 10);
//
//        tendermintStats.ifPresent(tendermintOverallStats::combine);
//        algorandStats.ifPresent(algorandOverallStats::combine);
//        mirStats.ifPresent(mirOverallStats::combine);
//      }
//
//      if (tendermintOverallStats.getCount() > 0 &&
//          tendermintOverallStats.getAverage() < tendermintBestLatency) {
//        tendermintBestLatency = tendermintOverallStats.getAverage();
//        tendermintBestTimeout = initalTimeout;
//      }
//      if (algorandOverallStats.getCount() > 0 &&
//          algorandOverallStats.getAverage() < algorandBestLatency) {
//        algorandBestLatency = algorandOverallStats.getAverage();
//        algorandBestTimeout = initalTimeout;
//      }
//      if (mirOverallStats.getCount() > 0 &&
//          mirOverallStats.getAverage() < mirBestLatency) {
//        mirBestLatency = mirOverallStats.getAverage();
//        mirBestTimeout = initalTimeout;
//      }
//
//      System.out.printf("%.2f, %s, %s, %s\n",
//          initalTimeout,
//          tendermintOverallStats.getCount() > 0 ? tendermintOverallStats.getAverage() : "",
//          algorandOverallStats.getCount() > 0 ? algorandOverallStats.getAverage() : "",
//          mirOverallStats.getCount() > 0 ? mirOverallStats.getAverage() : "");
//    }
//
//    System.out.println();
//    System.out.printf("Tendermint best with timeout %.2f: %.4f\n",
//        tendermintBestTimeout, tendermintBestLatency);
//    System.out.printf("Algorand best with timeout %.2f: %.4f\n",
//        algorandBestTimeout, algorandBestLatency);
//    System.out.printf("Mir best with timeout %.2f: %.4f\n",
//        mirBestTimeout, mirBestLatency);
//    double secondBestLatency = Math.min(tendermintBestLatency, algorandBestLatency);
//    System.out.printf("Mir speedup: %.4f\n",
//        (secondBestLatency - mirBestLatency) / secondBestLatency);
//  }
//
//  private static Optional<DoubleSummaryStatistics> runTendermint(
//      double initialTimeout, int correctNodeCount, int failedNodeCount) {
//    Random random = new Random();
//    List<Node> nodes = new ArrayList<>();
//    for (int i = 0; i < correctNodeCount; ++i) {
//      EarthPosition position = EarthPosition.randomPosition(random);
//      nodes.add(new CorrectTendermintNode(position, initialTimeout));
//    }
//    for (int i = 0; i < failedNodeCount; ++i) {
//      EarthPosition position = EarthPosition.randomPosition(random);
//      nodes.add(new FailedNode(position));
//    }
//    Collections.shuffle(nodes, random);
//
//    Network network = new FullyConnectedNetwork(nodes, random);
//    Simulation simulation = new Simulation(network);
//    if (!simulation.run(TIME_LIMIT)) {
//      return Optional.empty();
//    }
//
//    List<Node> correctNodes = nodes.stream()
//        .filter(n -> n instanceof CorrectTendermintNode)
//        .collect(Collectors.toList());
//    if (!correctNodes.stream().allMatch(Node::hasTerminated)) {
//      System.out.println("WARNING: Not all Tendermint nodes terminated.");
//      return Optional.empty();
//    }
//
//    return Optional.of(correctNodes.stream()
//        .mapToDouble(Node::getTerminationTime)
//        .summaryStatistics());
//  }
//
//  private static Optional<DoubleSummaryStatistics> runAlgorand(
//      double initialTimeout, int correctNodeCount, int failedNodeCout) {
//    Random random = new Random();
//    List<Node> nodes = new ArrayList<>();
//    for (int i = 0; i < correctNodeCount; ++i) {
//      EarthPosition position = EarthPosition.randomPosition(random);
//      nodes.add(new CorrectAlgorandNode(position, initialTimeout));
//    }
//    for (int i = 0; i < failedNodeCout; ++i) {
//      EarthPosition position = EarthPosition.randomPosition(random);
//      nodes.add(new FailedNode(position));
//    }
//    Collections.shuffle(nodes, random);
//
//    Network network = new FullyConnectedNetwork(nodes, random);
//    Simulation simulation = new Simulation(network);
//    if (!simulation.run(TIME_LIMIT)) {
//      return Optional.empty();
//    }
//
//    List<Node> correctNodes = nodes.stream()
//        .filter(n -> n instanceof CorrectAlgorandNode)
//        .collect(Collectors.toList());
//    if (!correctNodes.stream().allMatch(Node::hasTerminated)) {
//      System.out.println("WARNING: Not all Algorand nodes terminated.");
//      return Optional.empty();
//    }
//
//    //System.out.println("Algorand times: " + correctNodes.stream().mapToDouble(Node::getTerminationTime).sorted().boxed().collect(Collectors.toList()));
//    return Optional.of(nodes.stream()
//        .mapToDouble(Node::getTerminationTime)
//        .summaryStatistics());
//  }
//
//  private static Optional<DoubleSummaryStatistics> runMir(
//      double initialTimeout, int correctNodeCount, int failedNodeCount) {
//    Random random = new Random();
//    List<Node> nodes = new ArrayList<>();
//    for (int i = 0; i < correctNodeCount; ++i) {
//      EarthPosition position = EarthPosition.randomPosition(random);
//      nodes.add(new CorrectMirNode(position, initialTimeout));
//    }
//    for (int i = 0; i < failedNodeCount; ++i) {
//      EarthPosition position = EarthPosition.randomPosition(random);
//      nodes.add(new FailedNode(position));
//    }
//    Collections.shuffle(nodes, random);
//
//    Network network = new FullyConnectedNetwork(nodes, random);
//    Simulation simulation = new Simulation(network);
//    if (!simulation.run(TIME_LIMIT)) {
//      return Optional.empty();
//    }
//
//    List<Node> correctNodes = nodes.stream()
//        .filter(n -> n instanceof CorrectMirNode)
//        .collect(Collectors.toList());
//    if (!correctNodes.stream().allMatch(Node::hasTerminated)) {
//      System.out.println("WARNING: Not all Mir nodes terminated.");
//      return Optional.empty();
//    }
//
//    //System.out.println("Mir times: " + correctNodes.stream().mapToDouble(Node::getTerminationTime).sorted().boxed().collect(Collectors.toList()));
//    return Optional.of(nodes.stream()
//        .mapToDouble(Node::getTerminationTime)
//        .summaryStatistics());
//  }
//
//  private static String statisticsToCompactString(DoubleSummaryStatistics statistics) {
//    return String.format("min=%.2f, max=%.2f, average=%.2f",
//        statistics.getMin(), statistics.getMax(), statistics.getAverage());
//  }
//}
import java.util.*;
import java.util.stream.Collectors;

public class Main {
  private static final int RANDOM_SEED = 12345;
  private static final double TIME_LIMIT = 4;
  private static final int SAMPLES = 1000;
  private static final double reward = 0.5;   //出块奖励
  private static final double beta = 0.8;           //普通节点奖励占比
  private static final int round=60;
  private static final int correctNodeCnt=90; //正常节点
  private static final int failNodeCnt=10;  //恶意节点
  private static final int token=20;
  private static final double credit=0.5;
  private static final int delegetNum=21;

  public static void main( String[] args) {
    // Print the first row which contains column names.
//    System.out.println("initial_timeout, this_work");
//
//      List<Double> DPOSPTOverallStats = runDPOSPT(0.1, correctNodeCnt, failNodeCnt, token, credit, round, delegetNum);
//      List<Double> DPOSOverallStats = runDPOS(0.1, correctNodeCnt, failNodeCnt, token, credit, round, 21);
//      double DPoSPTRechooseRate = DPOSPTOverallStats.get(0), DPoSPTFailNodeRate = DPOSPTOverallStats.get(1), DPoSPTgini = DPOSPTOverallStats.get(2), avgTime = DPOSPTOverallStats.get(3);
////    double pbftAvgTime= DPOSPTOverallStats.get(4);
//      double DPoSRechooseRate = DPOSOverallStats.get(0), DPoSFailNodeRate = DPOSOverallStats.get(1), DPoSgini = DPOSOverallStats.get(2);
//      System.out.printf("%s: %s,\n",
//              "DPoSPT作恶节点重选率", DPoSPTRateRechoose
//      );
//      System.out.printf("%s: %s,\n",
//              "DPoSPT共识节点中作恶节点比率", DPoSPTFailNodeRate
//      );
//      System.out.printf("%s: %s,\n",
//              "DPoSPT平均区块确认时间", avgTime
//      );
////    System.out.printf("%s: %s,\n",
////            "PBFT平均共识时间",pbftAvgTime
////    );
//
//      System.out.printf("%s: %s,\n",
//              "DPoSPT财富基尼系数", DPoSPTgini
//      );
//      System.out.printf("%s: %s,\n",
//              "DPoS作恶节点重选率", DPoSRechooseRate
//      );
//      System.out.printf("%s: %s,\n",
//              "DPoS共识节点中作恶节点比率", DPoSFailNodeRate
//      );
//      System.out.printf("%s: %s,\n",
//              "DPoS财富基尼系数", DPoSgini
//      );
//    double dposAvgFailRate = getAvgFailRate(100,"DPoS");
//    double dposptAvgFailRate = getAvgFailRate(100,"DPoSPT");
//    System.out.println("dposAvgFailRate:"+dposAvgFailRate);
//    System.out.println("dposptAvgFailRate:"+dposptAvgFailRate);

//    double dposAvgGini = getAvgGini(100,"DPoS");
//    double dposptAvgGini = getAvgGini(100,"DPoSPT");
//    System.out.println("dposAvgGini:"+dposAvgGini);
//    System.out.println("dposptAvgGini:"+dposptAvgGini);

    //double dposptAvgTime = getAvgTime(10);
    //System.out.println("dposptAvgTime:"+dposptAvgTime);
    double dposptGini = getAvgGini(10,"DPoSPT");
    System.out.println(dposptGini);


//    System.out.printf("Mir speedup: %.4f\n",
//            (secondBestLatency - mirBestLatency) / secondBestLatency);
  }

//  private static DoubleSummaryStatistics runDPOS(
//          double initialTimeout, int correctNodeCount, int failedNodeCout, double token, double credit, int round, int delegateNum) {
//    Random random = new Random();
//    List<Node> nodes = new ArrayList<>();
//    List<Node> delegates;
//    Optional<DoubleSummaryStatistics> tempSummaryStatistics;
//    DoubleSummaryStatistics DPOSOverallStats = new DoubleSummaryStatistics();
//    for (int i = 0; i < correctNodeCount; ++i) {
//      EarthPosition position = EarthPosition.randomPosition(random);
//      nodes.add(new CorrectDPOSNode(position, initialTimeout, credit, token));
//    }
//    for (int i = 0; i < failedNodeCout; ++i) {
//      EarthPosition position = EarthPosition.randomPosition(random);
//      nodes.add(new FailedNode(position,credit,token));
//    }
//    Collections.shuffle(nodes, random);
//    for(int i = 0; i < 50 ; i++) {
//      delegates = getNodesByCredit(nodes, delegateNum);
//      Collections.shuffle(delegates, random);
//      List<Node> selectedDelegates = delegates.subList(0, delegates.size());
//      Network network = new FullyConnectedNetwork(selectedDelegates, random);
//
//
//      List<Node> correctNodes = selectedDelegates.stream()
//              .filter(n -> n instanceof CorrectDPOSNode)
//              .collect(Collectors.toList());
//
//      //System.out.println("Algorand times: " + correctNodes.stream().mapToDouble(Node::getTerminationTime).sorted().boxed().collect(Collectors.toList()));
//      tempSummaryStatistics = Optional.of(nodes.stream()
//              .mapToDouble(Node::getTerminationTime)
//              .summaryStatistics());
//      tempSummaryStatistics.ifPresent(DPOSOverallStats::combine);
//      for (Node node : selectedDelegates) {
//        if (node instanceof CorrectDPOSNode) {
//          node.setCredit(node.getCredit() <= 0.5 ? node.getCredit() + 0.01 : 0.5 - 0.5 * Math.cos(Math.PI * node.getCredit()));
//          node.setToken(node.getToken() + 1);
//          node.restart();
//          System.out.println(node.getToken());
//        } else {
//          node.setCredit(node.getCredit() / 10.0);
//        }
//      }
//
//    }
//    return DPOSOverallStats;
//  }

  private static List<Double> runDPOS(
          double initialTimeout, int correctNodeCount, int failedNodeCout, double token, double credit, int round, int delegateNum) {
    Random random = new Random();
    List<Node> nodes = new ArrayList<>();
    List<Node> delegates;
    List<Double> DPOSTokenOverallStats;
    List<Node> lastDelegates = new ArrayList<>();
    List<Double> rechooseRateList = new ArrayList<>(),DPOSOverallStatsList = new ArrayList<>(),failRateList = new ArrayList<>();
    int failCount=0;
    for (int i = 0; i < correctNodeCount; ++i) {
      EarthPosition position = EarthPosition.randomPosition(random);
      nodes.add(new DPoSNode(position, initialTimeout, credit, token));
    }
    for (int i = 0; i < failedNodeCout; ++i) {
      EarthPosition position = EarthPosition.randomPosition(random);
      nodes.add(new FailedNode(position,credit,token));
    }
    Collections.shuffle(nodes, random);

    for(int r = 0;r < round;r++){                                            //模拟n轮投票
      HashMap<Node,Double> voteTimeMap = new HashMap<>();

      HashMap<Node, List<Node>> votesMap = getVotes(nodes,"DPoS");    //模拟投票
//      delegates = getDelegatesByVotes(votesMap, delegateNum);      //选出见证人
      delegates = getDelegatesByVotes(votesMap, delegateNum*2);      //选出共识见证人和备选见证人

      delegates = delegates.subList(0, delegates.size() / 2);  //选出共识见证人节点
      System.out.println("共识见证人节点：");
      for (Node delegate : delegates) {
        System.out.println(delegate.toString());
      }


      failRateList.add(Double.valueOf(delegates.stream().filter(n->n instanceof FailedNode).count())/delegates.size());
      if(!lastDelegates.isEmpty()){
        List<Node> finalLastDelegates = lastDelegates;
        double lastFailNodes = finalLastDelegates.stream().filter(n->n instanceof FailedNode).count();
        double rechooseRate = lastFailNodes == 0?0.0:delegates.stream().filter(n->n instanceof FailedNode && finalLastDelegates.contains(n)).count()/lastFailNodes;
        rechooseRateList.add(rechooseRate);
        lastDelegates = new ArrayList<>(delegates);
      }else{
        lastDelegates = new ArrayList<>(delegates);
      }

      Collections.shuffle(delegates, random);                                 //随机打乱

      Network network = new FullyConnectedNetwork(delegates, random);
      for (int i = 0; i < delegateNum; i++) {                                  //这里每个共识见证人出块一次
        network.setCreatorId(i);
        Node creator = network.getCreator();
        if(creator instanceof DPoSNode) {
          creator.setToken(creator.getToken() + reward);//更新生产节点奖励
          System.out.println("更新生产节点奖励");
          System.out.println(creator.getToken() + reward);
          System.out.println(creator.toString());
        } else{
          failCount++;
        }
      }
    }
    DPOSTokenOverallStats = nodes.stream().filter(node -> node.getToken()>=0)
            .map(Node::getToken)
            .collect(Collectors.toList());//统计金额
//    DPOSOverallStatsList.add(round>1?rechooseRateList.stream().mapToDouble(n->n).average().getAsDouble():0.);
    DPOSOverallStatsList.add(failRateList.stream().mapToDouble(n->n).average().getAsDouble());
    DPOSOverallStatsList.add(gini(DPOSTokenOverallStats));
//    System.out.println(round*delegateNum-failCount);
//    nodes.stream().filter(node -> node instanceof DPoSNode).forEach(x->System.out.println(x.getCredit()+":"+x.getToken()));
//    nodes.stream().filter(node -> node instanceof FailedNode).forEach(x->System.out.println(x.getCredit()+":"+x.getToken()));
//    rechooseRateList.stream().forEach(x->System.out.println(x));
    return DPOSOverallStatsList;
  }

  private static List<Double> runDPOSPT(
          double initialTimeout, int correctNodeCount, int failedNodeCout, double token, double credit, int round, int delegateNum) {
    Random random = new Random();
    List<Node> nodes = new ArrayList<>();
    List<Node> delegates;
    Optional<DoubleSummaryStatistics> tempSummaryStatistics;
    DoubleSummaryStatistics DPOSPTTimeOverallStats = new DoubleSummaryStatistics();
    List<Double> DPOSPTTokenOverallStats = new ArrayList<>();
    List<Double> timeList = new ArrayList<>();
    List<Double> DPOSPTOverallStatsList = new ArrayList<>(),failRateList = new ArrayList<>();
    List<Node> lastDelegates = new ArrayList<>();          //记录上次见证人
    boolean isFirst=false;
    int failCount=0;

    for (int i = 0; i < correctNodeCount; ++i) {
      EarthPosition position = EarthPosition.randomPosition(random);
      nodes.add(new DPoSPTNode(position, initialTimeout, credit, token));
    }
    for (int i = 0; i < failedNodeCout; ++i) {
      EarthPosition position = EarthPosition.randomPosition(random);
      nodes.add(new FailedNode(position,credit,token));
    }
    Collections.shuffle(nodes, random);
    double broadcastTime=0;
    for(int r = 1;r <= round;r++){                                            //模拟n轮投票
      HashMap<Node,Double> voteTimeMap = new HashMap<>();
      for(Node node:nodes){
        voteTimeMap.put(node,random.nextGaussian());                       //正态模拟投票时间
      }
      HashMap<Node, List<Node>> votesMap = getVotes(nodes,"DPoSPT");//获取每个节点对应的投票者
      delegates = getDelegatesByVotes(votesMap, delegateNum*2);      //选出共识见证人和备选见证人

      List<Node> selectedDelegates = delegates.subList(0, delegates.size() / 2);  //选出共识见证人节点
      System.out.println("共识见证人节点：");
      for (Node delegate : selectedDelegates) {
        System.out.println(delegate.getPosition());
      }

      failRateList.add(Double.valueOf(selectedDelegates.stream().filter(n->n instanceof FailedNode).count())/selectedDelegates.size());
      List<Node> backupDelegates = delegates.subList(delegates.size() / 2,delegates.size()); //选出备用节点

      if(lastDelegates.isEmpty()){
        lastDelegates = new ArrayList<>(selectedDelegates);
        isFirst = true;
      }else if(isFirst){
        List<Node> finalLastDelegates = lastDelegates;
        double lastFailNodes = finalLastDelegates.stream().filter(n->n instanceof FailedNode).count();
        double rechooseRate = lastFailNodes == 0?0.0:delegates.stream().filter(n->n instanceof FailedNode && finalLastDelegates.contains(n)).count()/lastFailNodes;
        //        DPOSPTOverallStatsList.add(rechooseRate);
        isFirst=false;
      }
      PriorityQueue<Node> backupDelegatesQueue = new PriorityQueue<Node>(new Comparator<Node>() {
        @Override
        public int compare(Node o1, Node o2) {
          return (int)Math.signum(o2.getCredit() - o1.getCredit());
        }
      });                                                                        //构建一个最大堆存备选节点
      backupDelegatesQueue.addAll(backupDelegates);
      Collections.shuffle(delegates, random);                                 //随机打乱

      Network network = new FullyConnectedNetwork(selectedDelegates, random);
      for (int i = 0; i < delegateNum; i++) {                                  //这里每个共识见证人出块一次
//      delegates = getNodesByCredit(nodes, delegateNum);
        network.setCreatorId(i);
        Simulation simulation = new Simulation(network);
//      for(Node node:nodes){
//        if(node instanceof DPoSPTNode){DPoSPTNode node1 =(DPoSPTNode)node;System.out.println(node1.getLastBlock());}
//      }
        if (!simulation.run(TIME_LIMIT)) {                                  //运行共识
          System.out.println("out of time");
          continue;
        }
        List<DPoSPTNode> correctNodes = selectedDelegates.stream()
                .filter(n -> n instanceof DPoSPTNode).map(node -> (DPoSPTNode) node)
                .collect(Collectors.toList());
        Proposal output = correctNodes.get(0).getOutput();
        if (output != null && !correctNodes.stream().allMatch(Node::hasTerminated)) {
          System.out.println("WARNING: Not all DPOSPT nodes terminated.");
          break;
        }

        Node creator = network.getCreator();
        if (output != null) {
          double maxTime = voteTimeMap.entrySet().stream().mapToDouble(entry->entry.getValue()).max().getAsDouble();
          double minTime = voteTimeMap.entrySet().stream().mapToDouble(entry->entry.getValue()).min().getAsDouble();
          double totalToken = votesMap.get(creator).stream().mapToDouble(node->(1.0 + 9/(maxTime -minTime)*(maxTime - voteTimeMap.get(node)))*node.getCredit()*node.getToken()).sum();
          votesMap.get(creator).stream().filter(node->node!=creator).forEach(node -> node.setToken(node.getToken() +  (1.0 + 9/(maxTime -minTime)*(maxTime - voteTimeMap.get(node)))*beta*reward*node.getToken()*node.getCredit()/totalToken));  //更新投票节点奖励
          creator.setToken(creator.getToken() + (1.0-beta)*reward);   //更新生产节点奖励
          System.out.println("生产节点："+creator.toString());
          creator.setCredit(creator.getCredit() <= 0.5 ? creator.getCredit() + 0.01 : 0.5 - 0.5 * Math.cos(Math.PI * creator.getCredit()));    //更新信任度
          System.out.println("更新生产节点的奖励: "+creator.getToken());
          System.out.println("更新信任度："+creator.getCredit());
          nodes.stream().filter(node -> !correctNodes.contains(node)).forEach(node -> node.addBlock(output.getBlock()));//更新其他节点账本
          broadcastTime = nodes.stream().mapToDouble(node->network.getLatency(node,creator)).max().getAsDouble();
          Block block=output.getBlock();
          System.out.println("block-Index: "+block.getIndex());
          System.out.println("block-PreviousHash: "+block.getPreviousHash());
          System.out.println("block-Timestamp: "+block.getTimestamp());
          System.out.println("block-Transactions: "+block.getTransactions().toString());


        }else{
//          System.out.println("作恶");//节点作恶，更换节点
          System.out.println("作恶节点："+creator.toString());
          creator.setCredit(creator.getCredit()/10.0);                          //更新信任度
          network.getNodes().set(i % nodes.size(),backupDelegatesQueue.poll());  //取信任度最大的节点替代
          backupDelegatesQueue.add(creator);
          failCount++;
        }                                                                                              //
        correctNodes.stream().forEach(n -> n.resetPhase());                       //重置共识状态


        //System.out.println("Algorand times: " + correctNodes.stream().mapToDouble(Node::getTerminationTime).sorted().boxed().collect(Collectors.toList()));
        double finalBroadcastTime = broadcastTime;
//        tempSummaryStatistics = Optional.of(nodes.stream().filter(node -> node.getTerminationTime()>0)
//                .mapToDouble(node->node.getTerminationTime())
//                .summaryStatistics());
        OptionalDouble maxTime= nodes.stream().filter(node -> node.getTerminationTime()>0)
                .mapToDouble(node->node.getTerminationTime()+ finalBroadcastTime)
                .max();
        if(maxTime.isPresent()) timeList.add(maxTime.getAsDouble());
        //前五个区块确认时延
//        if(r%10==0&&i<6){
//          System.out.println(nodes.stream().filter(node -> node.getTerminationTime()>0)
//                  .mapToDouble(node->1000*(node.getTerminationTime()+ finalBroadcastTime)).max());
//        }

//        tempSummaryStatistics.ifPresent(DPOSPTTimeOverallStats::combine);           //统计出块时间
//        nodes.stream()
//                .forEach(node -> System.out.println(node.getTerminationTime()));
//        for (Node node : selectedDelegates) {
//          if (node instanceof DPoSPTNode) {
//            node.setCredit(node.getCredit() <= 0.5 ? node.getCredit() + 0.01 : 0.5 - 0.5 * Math.cos(Math.PI * node.getCredit()));    //更新信任度
//            node.setToken(node.getToken() + 1);
//            node.restart();
//            System.out.println(node.getToken());
//          } else {
//            node.setCredit(node.getCredit() / 10.0);
//          }
//        }
        selectedDelegates.stream().forEach(node ->node.restart());
      }
    }
//    nodes.stream().forEach(node -> System.out.println(node instanceof DPoSPTNode?"好节点"+node.getCredit():"坏节点"+node.getCredit()));
    DPOSPTTokenOverallStats = nodes.stream().filter(node -> node.getToken()>=0)
            .map(Node::getToken)
            .collect(Collectors.toList());
    double gini = gini(DPOSPTTokenOverallStats); //获取基尼系数
//    double PBFTAvgTime = DPOSPTTimeOverallStats.getAverage();  //获取平均区块确认时延
    double avgTime = timeList.stream().mapToDouble(n->n).average().getAsDouble();
    DPOSPTOverallStatsList.add(failRateList.stream().mapToDouble(n->n).average().getAsDouble());
//    System.out.println(failRateList.toString());
    DPOSPTOverallStatsList.add(gini);
    DPOSPTOverallStatsList.add(avgTime);
//    DPOSPTOverallStatsList.add(PBFTAvgTime);
//    nodes.stream().filter(node -> node instanceof FailedNode).forEach(x->System.out.println(x.getCredit()+":"+x.getToken()));
//    System.out.println(nodes.stream().filter(node -> node instanceof DPoSPTNode)
//            .mapToDouble(node -> ((DPoSPTNode) node).getBlockChain().get(((DPoSPTNode) node).getBlockChain().size()-1).getIndex())
//            .max().getAsDouble());
//    System.out.println(failCount);
    return DPOSPTOverallStatsList;
  }
  public static double gini(List<Double> values) {
    double sumOfDifference = values.stream()
            .flatMapToDouble(v1 -> values.stream().mapToDouble(v2 -> Math.abs(v1 - v2))).sum();
    double mean = values.stream().mapToDouble(v -> v).average().getAsDouble();
    return sumOfDifference / (2 * values.size() * values.size() * mean);
  }

  private static String statisticsToCompactString(DoubleSummaryStatistics statistics) {
    return String.format("min=%.2f, max=%.2f, average=%.2f",
            statistics.getMin(), statistics.getMax(), statistics.getAverage());
  }

  private static HashMap<Node,List<Node>> getVotes(List<Node> nodes, String type){
    HashMap<Node,List<Node>> result = new HashMap<>();
    Random random = new Random(1234);
    for(Node node:nodes){
      double max = 0;int randint = random.nextInt(nodes.size()),maxId = randint,num = type.equals("DPoS")? nodes.size() : nodes.size()/2;
      for(int i = randint;i<randint +num;i++){           //DPoSPT随机选出一半投票
        double feature = type.equals("DPoS")?Math.pow(random.nextDouble(),1/nodes.get(i%nodes.size()).getToken()):Math.pow(random.nextDouble(),1/(nodes.get(i%nodes.size()).getCredit()*nodes.get(i%nodes.size()).getToken()));
        if( feature> max){
          max = feature;
          maxId = i%nodes.size();
        }
      }
      if(result.containsKey(nodes.get(maxId))){
        result.get(nodes.get(maxId)).add(node);
      }else {
        // result.put(nodes.get(maxId),new ArrayList<>(){{add(node);}});
        List<Node> list = new ArrayList<>();
        list.add(node);
        result.put(nodes.get(maxId),list);
      }
    }
    for(Node node:nodes){
      Set<Node> nodeSet = result.keySet();
      if(!nodeSet.contains(node)){
        result.put(node,new ArrayList<>());
      }
    }
    return result;
  }


  private static List<Node> getDelegatesByVotes(HashMap<Node,List<Node>> votesMap, int delegateNum){
    HashMap<Node,Double> votes = new HashMap<>();
    for(Map.Entry<Node,List<Node>> entry:votesMap.entrySet()){
      votes.put(entry.getKey(),entry.getValue().stream().mapToDouble(node -> node.getCredit()* node.getToken()).sum());
    }
    List<Map.Entry<Node,Double>> list = new ArrayList<Map.Entry<Node,Double>>(votes.entrySet());
    List<Node> result = new ArrayList<>();
    Collections.sort(list, new Comparator<Map.Entry<Node, Double>>() {
      @Override
      public int compare(Map.Entry<Node, Double> o1, Map.Entry<Node, Double> o2) {
        return o2.getValue().compareTo(o1.getValue());
      }
    });
    for( Map.Entry<Node,Double> e :list.subList(0,delegateNum)){
      result.add(e.getKey());
    }
    return result;
  }


  private  static  List<Node> getNodesByCredit(List<Node> nodes, int n){
    List<Node> result = new ArrayList<>();
    Random random = new Random(1234);
    HashMap<Node,Double> nodeCreditMap = new HashMap<>();
    PriorityQueue<Node> minHeap = new PriorityQueue<Node>(new Comparator<Node>(){
      @Override
      public int compare(Node n1, Node n2){
        return  (int) Math.signum(nodeCreditMap.get(n1) - nodeCreditMap.get(n2));
      }
    });
    for(Node node:nodes){
      nodeCreditMap.put(node,Math.pow(random.nextDouble(),1/node.getCredit()));
      if(minHeap.size() < n) {
        minHeap.add(node);
      }else{
        double value = Math.pow(random.nextDouble(),1/node.getCredit());
        if(nodeCreditMap.get(minHeap.peek()) < value){
          minHeap.poll();
          minHeap.add(node);
        }
      }
    }
    for(Node node:minHeap){
      result.add(node);
    }
    return result;
  }

  public static double getAvgFailRate(int n,String type){
    double sum=0;
    for(int i=0;i<n;i++){
      if(type.equals("DPoS")){
      List<Double> DPOSOverallStats = runDPOS(0.1, correctNodeCnt, failNodeCnt, token, credit, round, delegetNum);
      sum+=DPOSOverallStats.get(0);
      }else{
        List<Double> DPOSPTOverallStats = runDPOSPT(0.1, correctNodeCnt, failNodeCnt, token, credit, round, delegetNum);
        sum+=DPOSPTOverallStats.get(0);
      }
    }
    return sum/n;
  }

  public static double getAvgGini(int n,String type){
    double sum=0;
    for(int i=0;i<n;i++){
      if(type.equals("DPoS")){
        List<Double> DPOSOverallStats = runDPOS(0.1, correctNodeCnt, failNodeCnt, token, credit, round, delegetNum);
        sum+=DPOSOverallStats.get(1);
      }else{
        List<Double> DPOSPTOverallStats = runDPOSPT(0.1, correctNodeCnt, failNodeCnt, token, credit, round, delegetNum);
        sum+=DPOSPTOverallStats.get(1);
      }
    }
    return sum/n;
  }

  public static double getAvgTime(int n){
    double sum=0;
    for(int i=0;i<n;i++){
        List<Double> DPOSPTOverallStats = runDPOSPT(0.1, correctNodeCnt, failNodeCnt, token, credit, round, delegetNum);
        sum+=DPOSPTOverallStats.get(2);
    }
    return sum/n;
  }
}
